use arc_swap::ArcSwap;
use axum::{
    Extension, Router,
    extract::Query,
    http::StatusCode,
    response::{Html, IntoResponse},
    routing::{get, post},
};
use axum_demo::{ParamConfig, SECRET, ServerConfig, UserStore, chat, create_user, get_user};
use jsonwebtoken::{self as jwd, EncodingKey};
use serde::Deserialize;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let config = ServerConfig::load().await;
    // 外层Arc为ArcSwap要求struct需实现Clone
    // 内层Arc为Axum要求, Extension共享的struct需实现Clone
    let param = Arc::new(ArcSwap::new(Arc::new(config.param)));

    let user_store = UserStore::default();
    let chat_state = chat::handle::State::new();

    let app = Router::new()
        .route("/", get(index_handle))
        .route("/reload", post(reload_handle))
        .route(
            "/user",
            post(create_user)
                .get(get_user)
                .layer(Extension(user_store.clone())),
        )
        .route("/login", post(login_handle))
        .route(
            "/ws/chat",
            get(chat::handle::ws_handle).layer(Extension(chat_state)),
        )
        .layer(Extension(param));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

type ParamConfigRef = Arc<ArcSwap<ParamConfig>>;

async fn index_handle(Extension(param): Extension<ParamConfigRef>) -> Html<String> {
    let param = param.load();
    let msg = format!("max_size: {}, min_size: {}", param.max_size, param.min_size);
    tracing::info!(msg);
    Html(msg)
}

async fn reload_handle(Extension(param): Extension<ParamConfigRef>) -> impl IntoResponse {
    let config = ServerConfig::load().await;
    param.store(Arc::new(config.param));
    "Reloading..."
}

////////////////////////////

#[derive(Deserialize)]
struct LoginRequest {
    id: String,
}

#[axum::debug_handler]
async fn login_handle(Query(params): Query<LoginRequest>) -> impl IntoResponse {
    jwd::encode(
        &jwd::Header::default(),
        &params.id,
        &EncodingKey::from_secret(SECRET),
    )
    .map_err(|e| {
        tracing::error!("encode token failed: {}", e);
        StatusCode::INTERNAL_SERVER_ERROR
    })
}
